现有 api 和hooks 总结

React 稳定版常用 API & Hooks 总表(不含实验特性)

API / Hook作用基本用法常见场景面试高频点 / 注意事项
createElement创建 React 元素(JSX 底层)React.createElement('div', null, 'Hello')JSX 编译后底层实现JSX 本质不是 HTML,而是 createElement
Fragment避免额外 DOM 包裹<></>返回多个元素不生成真实 DOM
StrictMode开发模式检查潜在问题<React.StrictMode>检查副作用开发环境会触发额外 render
Suspense处理异步加载 fallback<Suspense fallback={<Loading />}>懒加载、数据加载常配合 lazy
lazy动态导入组件const A = lazy(() => import('./A'))路由拆包必须配合 Suspense
memo缓存组件,避免重复渲染memo(Component)性能优化浅比较 props
forwardRef父组件传 ref 给子组件forwardRef((props, ref)=>{})获取子组件 DOM常搭配 useImperativeHandle
createContext创建上下文const Ctx = createContext()跨层级传值避免 props drilling
createRef创建 ref(类组件常用)this.ref = createRef()DOM 操作函数组件通常用 useRef

基础 Hooks

Hook作用基本用法常见场景面试高频点 / 注意事项
useState状态管理const [count, setCount] = useState(0)表单、计数器异步更新、批处理
useEffect副作用处理useEffect(()=>{}, [])请求、监听、定时器清理函数防内存泄漏
useContext获取 Context 数据useContext(MyContext)全局主题、用户信息Provider value 改变会触发消费组件更新
useReducer复杂状态管理useReducer(reducer, init)多状态逻辑类似 Redux
useRef持久引用,不触发渲染const ref = useRef()DOM、缓存变量改变 .current 不 rerender

性能优化 Hooks

Hook作用基本用法常见场景面试高频点 / 注意事项
useMemo缓存计算结果useMemo(()=>fn(), [deps])大计算缓存值
useCallback缓存函数引用useCallback(()=>{}, [deps])子组件 props缓存函数
useTransition标记低优先级更新const [isPending, startTransition] = useTransition()搜索、过滤提升交互流畅度
useDeferredValue延迟值更新const deferred = useDeferredValue(value)输入搜索优化类似防抖但不是防抖

DOM / 生命周期 Hooks

Hook作用基本用法常见场景面试高频点 / 注意事项
useLayoutEffectDOM 更新后、绘制前执行useLayoutEffect(()=>{})测量 DOM会阻塞绘制
useImperativeHandle自定义 ref 暴露内容useImperativeHandle(ref, ()=>({...}))暴露子组件方法配合 forwardRef
useId生成稳定唯一 IDconst id = useId()表单 label/inputSSR 防止 hydration mismatch
useSyncExternalStore外部状态同步useSyncExternalStore(subscribe, getSnapshot)Redux/ZustandReact 18 外部状态标准
useInsertionEffect样式插入前执行CSS-in-JS 库使用styled-components很少业务直接用

React DOM API

API作用基本用法注意事项
createRootReact 18 新渲染入口createRoot(root).render(<App />)替代 ReactDOM.render
hydrateRootSSR hydrationhydrateRoot(el, <App />)Next.js SSR 常见
flushSync强制同步更新flushSync(()=>setX())谨慎使用

Hooks 使用规则(面试必考)

规则说明
只能在函数组件或自定义 Hook 顶层调用不能在循环/条件/嵌套函数里
必须保持调用顺序一致React 靠顺序记录 Hook
自定义 Hook 必须 use 开头规范 + lint 检查

高频对比(面试非常常见)

对比核心区别
useState vs useRefstate 改变会触发渲染;ref 不触发
useEffect vs useLayoutEffecteffect 异步;layoutEffect 同步阻塞
useMemo vs useCallbackmemo 缓存值;callback 缓存函数
Context vs ReduxContext 传值;Redux 是更强的状态管理
createRef vs useRefcreateRef 每次新建;useRef 持久

一句话记忆(适合面试口语)

面试官最爱问的坑点

  1. 为什么 useEffect 会执行两次?

因为 React StrictMode 开发模式故意检查副作用。

  1. 为什么 setState 不立即更新?

因为批处理 + 调度机制。

  1. 为什么不要滥用 useMemo/useCallback

因为本身也有性能成本。

  1. ref 为什么不触发更新?

因为它不是响应式状态。

 

最推荐你的记忆顺序(前端面试)

先背: useState → useEffect → useRef → useMemo → useCallback → useContext → useReducer

再背: forwardRef → useImperativeHandle → useTransition → useSyncExternalStore

对于大部分前端面试,这套已经覆盖 90%+ React 问题。

 

Q1: useState 和普通变量(例如 let count = 0)的核心区别是什么?为什么 React 不直接用普通变量管理 UI?

useState 和普通变量最核心的区别有两个:

  1. 普通变量不会触发组件重新渲染

即使值变了,React 并不知道这个变化,所以 UI 不会自动更新。

  1. useState 会被 React 保存,并在更新时触发 rerender

调用 setCount 后:

  1. 普通变量在函数组件重新执行时会被重置

每次 render 都重新从 0 开始。

useState 的值不会因为函数重新执行而丢失,因为 React 是按 Hook 链表(或索引)保存状态。


一句话总结: 普通变量只能存值;useState 不仅存值,还能通知 React 更新界面。


Standard Answer (English)

The core difference between useState and normal variables is:

  1. Normal variables do not trigger re-render

Even if the value changes, React does not track it, so the UI will not update.

  1. useState is tracked by React

When setCount is called:

  1. Normal variables reset on every render

Function components re-run entirely, so local variables are recreated.

useState persists because React stores it outside the function body.


面试官为什么问这个?

因为这是在考你是否真正理解:


实战场景

错误写法:

页面不会更新。

 

正确写法:


常见误区

错误:“useState 就是一个普通变量升级版”

正确:“useState 本质是 React 的状态调度系统的一部分”


面试口语版(中文)

useState 和普通变量最大的区别是,普通变量变化不会让 React 重新渲染,而且组件重新执行时变量会重置。useState 的状态是 React 保存的,调用 setter 后 React 会触发更新,所以 UI 能同步变化。

 

Interview Spoken Answer (English)

The biggest difference is that normal variables are not reactive. Changing them will not trigger a re-render, and they reset every time the component function runs again. useState is managed by React internally, so when we call the setter, React updates the state and re-renders the component automatically.

 

 

Q2: useEffect 的作用是什么?它和直接把代码写在组件函数体里面有什么本质区别?

问题:

例如:为什么下面这种写法通常有问题?


  1. useEffect 的核心作用是:在 React 渲染完成后执行副作用代码,用于同步组件和 React 外部系统。

外部系统包括:

  1. 为什么不能直接写在组件函数体里?

因为组件函数体的本质是: Render 阶段(渲染阶段)

React 要求 render 必须:


错误示例:

这段代码的问题在于:每次组件重新渲染都会重新请求。

都会导致重复请求,甚至死循环。


正确写法:

This ensures the effect runs after render and only once on mount.

[] 的含义:只在首次挂载后执行一次。


本质区别

组件函数体:“描述 UI 应该长什么样”

useEffect:“UI 渲染后,需要顺便做什么事”


一句话总结:Render 用来计算界面,Effect 用来处理副作用。


Standard Answer (English)

  1. useEffect is used to run side effects after React finishes rendering.

Examples:

  1. Why not put side effects directly inside the component body?

Because the component body runs during the render phase, which should stay pure.

React may re-run the component many times, so this:

could trigger repeated requests on every render.


面试官为什么问这个?

因为这题在考:


实战场景

常见错误:

直接写组件里,可能造成:


常见误区

错误:“useEffect = componentDidMount”

正确:useEffect 更像 “render 后同步副作用”,不仅仅是 mount。


面试口语版(中文)

useEffect 主要用于处理副作用,比如请求接口、事件监听或者定时器。因为组件函数本身是 render 阶段,应该保持纯净,如果直接把 fetch 写在函数体里,每次重新渲染都会执行,容易造成重复请求。useEffect 可以让这些操作在渲染完成后按依赖控制执行。

 

Interview Spoken Answer (English)

useEffect is mainly used for side effects like API calls, event listeners, or timers. The component body itself is the render phase and should stay pure. If we put something like fetch directly inside it, it may run on every render and cause repeated requests. useEffect lets us run those side effects after rendering and control them with dependencies.

 

Q3: useEffect 的依赖数组(dependency array)到底是做什么的?

请你分别解释下面三种情况的区别,以及实际开发中各自适合什么场景:


useEffect 的第二个参数(依赖数组)本质上是: 告诉 React,当哪些依赖变化时,需要重新执行这个副作用。

React 会用浅比较(Object.is)检查依赖是否变化。

第一种:

含义:没有依赖数组 → 每次 render 后都执行

执行时机:

场景:很少用,因为容易造成性能问题。

风险:如果里面 setState,容易无限循环。

 

第二种:

含义:空依赖 → 只在首次挂载后执行一次

场景:

注意:

如果引用外部变量,但不加依赖,可能拿到旧值(stale closure)。

 

第三种:

含义:首次执行 + count 改变时重新执行

场景:

示例:


一句话总结


更底层理解

不是“执行次数控制器”,而是:副作用和依赖之间的同步声明。


Standard Answer (English)

The dependency array tells React when the effect needs to re-run based on specific reactive values.

1.

Runs after every render.

Use case:

Rarely useful, can cause performance issues.

 

2.

Runs only once after initial mount.

Use case:

 

3.

Runs on mount + whenever count changes.

Use case:

Syncing side effects with changing state.


面试官为什么问这个?

因为这题核心在考:


实战场景

搜索:

 

监听:


常见误区

错误:“[] 就等于 componentDidMount”

正确:更像“首次同步”,不是类组件生命周期的简单复制。


错误:“依赖不写更省事”

 

正确:

如果不写依赖可能导致:


面试口语版(中文)

依赖数组本质上是告诉 React,这个 effect 依赖哪些值变化。不写依赖就是每次渲染后都执行;空数组表示只在首次挂载执行一次;而写 [count] 表示首次挂载时执行,并且 count 改变时重新执行。实际开发里最重要的是保证副作用和依赖同步,否则容易出现无限循环或者拿到旧数据。

 

Interview Spoken Answer (English)

The dependency array tells React which values this effect depends on. Without it, the effect runs after every render. With an empty array, it runs only once after mount. With [count], it runs on mount and whenever count changes. In real projects, it’s mainly about keeping side effects synchronized with state changes and avoiding stale data or infinite loops.

 

Q4: 为什么 useEffect 里有时候需要返回一个 cleanup function(清理函数)?

请你结合真实开发场景解释:如果不清理,可能会出现什么问题?

例如:


useEffect 返回 cleanup function 的核心作用是: 在副作用失效前,撤销之前注册的外部资源,避免重复订阅、资源残留和状态错误。

 

常见需要清理的场景

  1. 事件监听

为什么必须清理?

如果组件卸载后不移除:

结果可能包括:

  1. 定时器
  1. WebSocket / Subscription
  1. Abort fetch

 

执行时机

Cleanup 会在两种情况下执行:


一句话总结:cleanup 的本质不是“清理代码”,而是撤销上一次副作用。


Standard Answer (English)

The cleanup function is used to remove or cancel side effects before they become invalid.

Its purpose is to:

 

Example:

Without cleanup:

Cleanup runs when:

  1. Component unmounts
  2. Before effect re-runs

面试官为什么问这个?

因为这是在考:


实战场景(非常常见)

错误:

组件卸载后 interval 还在跑。

 

正确的写法,需要加上清理函数:


常见误区

错误:“只有卸载时才 cleanup”

正确:依赖变化时也会 cleanup 上一次 effect。

 

错误:“fetch 不需要 cleanup”

正确:异步请求也可能需要 abort,避免竞态问题。


面试口语版(中文)

cleanup function 的作用是撤销上一次副作用,比如移除事件监听、清除定时器、关闭订阅。如果不清理,组件卸载后这些外部资源可能还继续运行,导致内存泄漏、重复执行,甚至更新已经卸载的组件。它本质上是保证副作用生命周期正确。

 

Interview Spoken Answer (English)

The cleanup function is used to undo previous side effects, such as removing event listeners, clearing timers, or unsubscribing from external resources. Without cleanup, those side effects may continue after the component unmounts, causing memory leaks, duplicate execution, or stale state updates. Its main purpose is to keep side effects properly synchronized with the component lifecycle.

 

Q27: useEffect的执行流程是什么?什么会触发useEffect重新执行?组件重新渲染会触发useEffect重新执行吗?

useEffect 并不是发生在 Render 阶段,而是在 Commit 阶段完成 DOM 更新以后 才执行。

整个执行流程如下:

所以:

useEffect 永远不会阻塞页面渲染。

页面已经更新完成之后,React 才会去执行 Effect。

React 是什么时候决定要不要执行 Effect?

React 在 Render 阶段就已经决定好了。

例如:

Render 的时候,React 会做两件事情:

然后使用

逐个比较 dependency。

React 内部类似:

如果发现有任何一个依赖变化:

React 就会给当前 Fiber 打上一个 Effect 标记(effect flag)。

真正执行则发生在 Commit 之后。

什么情况下会重新执行 useEffect?

很多人会回答:

状态变化。

其实更准确一点应该说:

组件发生一次新的 Render,并且依赖发生变化。

因为:

useEffect 从来不是"状态变化触发",而是 Render 完成以后根据 dependency 判断是否执行。

例如:

执行

流程:

真正触发 Effect 的不是 setState。

而是:

那组件重新 Render,会不会执行 useEffect?

答案:

不一定。

这是很多人容易答错的地方。

例如:

点击:

会发生:

因为 state 更新了。

但是:

React 比较以后:

于是:

所以:

组件重新 Render ≠ useEffect 一定重新执行。

必须依赖发生变化。

 

如果没有 dependency 呢?

例如:

没有第二个参数。

React 会认为:

所以:

每一次都会执行。

例如:

再:

都会执行。

 

空数组呢?

第一次:

以后:

所以:

表示:

只在组件挂载完成后的首次 Commit 后执行一次(开发模式下开启 Strict Mode 时会额外执行一次用于检查副作用)。

 

Dependency 是对象呢?

例如:

React 比较的是:

比较的是引用。

例如:

虽然内容一样:

但是:

因此:

 

Cleanup 什么时候执行?

例如:

执行流程:

第一次:

第二次:

可以记住一句话:

React 总是先清理上一次 Effect,再执行下一次 Effect。

组件卸载时:

也会执行一次清理函数。

 

完整生命周期流程

 

英文面试回答

useEffect runs after the component has been committed to the DOM. During the render phase, React compares the current dependency array with the previous one using Object.is(). If any dependency has changed, React marks the effect to run. After the commit phase, React first runs the cleanup function from the previous effect (if there is one), and then executes the new effect.

A component re-render alone does not guarantee that useEffect will run. The component must re-render, and at least one dependency must have changed. If the dependency array is omitted, the effect runs after every render. If it's an empty array ([]), it only runs after the initial mount (except for the extra development-only execution in Strict Mode).

 

面试官最喜欢追问的一句话

很多候选人会说:

"state 变化会触发 useEffect。"

更准确的回答应该是:

State 或 props 的变化会导致组件重新渲染(Render)。在 Render 阶段,React 会比较依赖数组;只有依赖发生变化,React 才会在 Commit 完成后执行 useEffect。因此,组件重新渲染只是必要条件,而不是充分条件。

 

 

Q28: 事件绑定到底是写成{handleClick}还是{() => handleClick()}

这是 React 面试里非常常见的问题。答案不是简单地说哪个对,而是要看你想传递什么

1. 推荐默认写法:{handleClick}

例如:

这里传递的是:

这个函数本身的引用

React 内部会在点击时调用:

所以等价于:

执行流程

2. {() => handleClick()} 也可以,但它创建了一个新函数

例如:

这里传递给 React 的不是 handleClick

而是:

一个新的匿名函数。

每次 Render:

所以引用不同:

 

3. 那什么时候必须用箭头函数?

当你需要传参数的时候。

例如:

如果直接写:

错误。

因为:

会立即执行。

Render 时:

但是 React 需要的是:

而不是执行结果。

 

4. 一个常见错误

❌ 错误:

因为:

Render 阶段:

点击按钮时:

 

5. 性能区别(面试重点)

假设:

和:

区别:

第一种:

第二种:

如果子组件:

第二种可能导致:

 

6. 那是不是永远不要写箭头函数?

不是。

实际开发:

简单事件

推荐:

需要传参数

使用:

列表场景

例如:

这是正常写法。

 

7. 面试标准回答(英文)

In React, we usually pass the function reference directly, like onClick={handleClick}. React will call this function automatically and pass the event object as the argument.

We use an arrow function like onClick={() => handleClick(id)} when we need to pass additional parameters or execute custom logic before calling the handler.

The difference is that the arrow function creates a new function on every render, which may cause unnecessary re-renders for memoized child components.

 

一句话记忆:

写法含义使用场景
{handleClick}传函数引用最常用
{handleClick()}立即执行❌错误
{() => handleClick()}创建新函数,点击时调用需要包装逻辑/传参数

面试时回答:

"Use onClick={handleClick} by default. Use onClick={() => handleClick(id)} only when you need to pass arguments or add extra logic."

 

 

Q5: useRefuseState 都可以“保存值”,那它们的本质区别是什么?

请你结合真实开发场景回答:

  1. 什么情况下你会用 useRef 而不是 useState
  2. 为什么修改 ref.current 不会触发重新渲染?
  3. useRef 除了操作 DOM,还有哪些常见用途?

useRefuseState 都能跨 render 保存值,但本质区别是:

  1. 核心区别

useState用于影响 UI 的响应式状态

特点:

 

useRef

用于保存不会影响 UI 的可变值

特点:

 

为什么 ref.current 不触发更新?

因为 React 的更新机制只监听:

ref 本质更像:一个持久化的普通对象容器

改它不会进入 React 调度流程。

 

useRef的实战场景

场景1:操作 DOM

场景2:保存定时器 ID

避免 rerender。

场景3:保存上一次值

场景4:避免 stale closure

保存最新 callback。


什么时候选 useRef 而不是 useState

useRef

useState


一句话总结useState 管 UI,useRef 管引用。


Standard Answer (English)

Both useState and useRef persist values across renders, but:

 

useState

Used for reactive state that affects UI.

 

useRef

Used for mutable values that should persist but not trigger UI updates.

 

Why?

Because useRef is just a persistent object:

React doesn’t schedule updates for it.

 

useRef common real-world uses:

DOM access

Timer IDs

Previous values

External instances

Avoiding stale closures


面试官为什么问这个?

因为这题考你:


常见误区

错误:“useRef 就是拿 DOM 的”

正确:DOM 只是其中一个用途,它更常用于持久化非 UI 数据。

 

错误:“useRef 不能改”

正确:可以改,但 React 不关心。


面试口语版(中文)

useStateuseRef 都可以跨渲染保存值,但 useState 是响应式状态,更新后会触发组件重新渲染,适合管理 UI。useRef 更像一个持久化容器,修改 .current 不会触发渲染,所以更适合保存 DOM、定时器、上一次值或者其他不影响界面的数据。

 

Interview Spoken Answer (English)

Both useState and useRef can persist values across renders, but useState is reactive and triggers re-render when updated, so it’s used for UI state. useRef is more like a persistent container whose .current value can change without causing re-render, so it’s useful for DOM references, timers, previous values, or other mutable data that doesn’t directly affect the UI.

 

Q6: useMemouseCallback 的区别是什么?它们分别解决什么问题?

请你结合实际开发说明:

  1. 它们各自缓存的是什么?
  2. 为什么 React 需要它们?
  3. 什么时候“用了反而更慢”?

一、核心区别(一句话)

  1. 它们分别缓存什么?

useMemo:缓存“值”

👉 返回的是“计算后的结果”

 

useCallback:缓存“函数”

👉 返回的是“函数引用本身”

 

  1. 本质关系(重点)

👉 useCallback 其实是 useMemo 的语法糖

 

  1. 为什么 React 需要它们?

React 默认行为:

每次 render:

问题1:昂贵计算重复执行

👉 每次 render 都算一次(浪费性能)

问题2:子组件不必要 rerender

👉 每次 render 都生成新函数 👉 子组件即使 memo 也会重新渲染

 

  1. useMemo / useCallback 解决什么问题?
Hook解决的问题
useMemo避免重复计算
useCallback避免函数引用变化导致子组件 rerender
  1. 什么时候“用了反而更慢”?

这是面试重点(很多人答不出来)

❌ 情况1:计算很简单

👉 useMemo 本身有缓存开销 👉 比直接计算更慢

 

❌ 情况2:子组件没优化

但 Child 没有 memo:

👉 用不用 useCallback 都一样 rerender

 

❌ 情况3:依赖变化频繁

如果 deps 每次都变:等于“白缓存”

 

  1. 正确使用场景

useMemo

 

useCallback


一句话总结

👉 useMemo 优化“计算” 👉 useCallback 优化“函数引用”


Standard Answer (English)

Core difference:

  1. What they cache

useMemo:

Returns a memoized value.

 

useCallback:

Returns a memoized function reference.

  1. Why React needs them

Because by default:

  1. When they can hurt performance

面试口语版(中文)

useMemo 是用来缓存计算结果的,避免每次 render 都做重复计算,比如排序或者过滤列表。useCallback 是用来缓存函数本身,避免函数引用变化导致子组件不必要的重新渲染。两者本质区别是一个缓存值,一个缓存函数。但如果计算很简单或者子组件没有做 memo 优化,用它们反而可能增加性能开销。

 

Interview Spoken Answer (English)

useMemo is used to memoize computed values to avoid expensive recalculations on every render. useCallback is used to memoize function references so that child components don’t re-render unnecessarily due to changed props. However, if the computation is trivial(琐碎的,不重要的) or the child component is not optimized with memo, using them can actually hurt performance instead of improving it.

 

Q7: useEffect 依赖项里如果写错了,会出现什么问题?

请结合实际开发回答:

  1. 依赖写少了会怎样?
  2. 依赖写多了会怎样?
  3. 为什么 React 不帮你自动处理依赖?

一、依赖写少了会怎样?

👉 会出现“数据过期 / 闭包旧值问题(stale closure)”

例子:

问题:

结果:

👉 UI 和逻辑不同步 👉 使用旧数据

 

二、依赖写多了会怎样?

👉 effect 频繁执行,引发性能问题甚至死循环

例子:

问题:

结果:

👉 无限循环请求 / 多次重复执行

 

三、依赖写错的本质问题

👉 React 无法“自动猜测你依赖什么”

因为React 的设计原则是:

side effect must be explicit(清楚明白的,明确的)

 

四、为什么 React 不帮你自动处理依赖?

核心原因有三个:

  1. JavaScript 是动态的

👉 React 无法静态分析真实依赖

  1. 闭包问题

effect 捕获的是“某一轮 render 的变量”

  1. 性能成本

如果 React 自动追踪: 👉 会变成“全量依赖追踪系统” 👉 性能不可接受

 

五、真实开发总结

❌ 依赖少会造成:

❌ 依赖多会造成:

 

✅ 正确做法:

一句话总结:dependency 写错 = React 逻辑同步失败


Standard Answer (English)

  1. Missing dependencies

Leads to stale closure issues:

  1. Extra dependencies

Leads to:

  1. Why React doesn’t auto-handle deps?

Because:

  1. JavaScript is dynamic and hard to statically analyze
  2. Effects rely on closures
  3. Automatic tracking would be too expensive performance-wise

面试口语版(中文)

如果 useEffect 依赖写少了,就会出现闭包拿到旧值的问题,导致数据不同步。如果写多了,会导致 effect 频繁执行,甚至可能引发无限循环。React 不帮我们自动管理依赖,是因为 JavaScript 是动态的,React 无法准确判断你真正依赖哪些变量,所以必须开发者自己显式声明。

 

Interview Spoken Answer (English)

If dependencies are missing, we may get stale closure issues where the effect uses outdated values. If we include too many dependencies, the effect may run too often and even cause infinite loops or performance issues. React doesn’t automatically manage dependencies because JavaScript is dynamic and closures make it hard to statically determine what values are truly used, so developers must explicitly declare them.

 

Q8: useEffectuseLayoutEffect 有什么区别?在真实项目中你会怎么选择?

请重点回答这几个点:

  1. 执行时机有什么不同?
  2. 会不会阻塞浏览器渲染?
  3. 什么场景必须用 useLayoutEffect

  1. 执行时机区别

useEffect

👉 不会阻塞页面显示

 

useLayoutEffect

在 DOM 更新完成(commit phase)后,浏览器绘制之前同步执行

👉 会阻塞浏览器绘制

  1. 是否阻塞渲染?
Hook是否阻塞渲染
useEffect❌ 不阻塞
useLayoutEffect✅ 阻塞
  1. 关键本质区别

useEffect:

👉 “先把页面画出来,再处理副作用”

useLayoutEffect:

👉 “先处理完 DOM,再让浏览器画”

  1. 为什么 useLayoutEffect 会阻塞?

因为它是:

所以 React 必须等它执行完才能继续绘制

  1. 真实项目怎么选?

默认用 useEffect(99%场景)

适合:

 

必须用 useLayoutEffect 的场景

  1. 面试关键点总结

👉 useLayoutEffect = “防闪烁工具”


一句话总结


Standard Answer (English)

  1. Timing difference

useEffect

Runs after the browser paints the screen (async).

useLayoutEffect

Runs after DOM updates but before paint (sync).

  1. Rendering impact
HookBlocks paint
useEffectNo
useLayoutEffectYes
  1. Core idea
  1. When to use useLayoutEffect

面试口语版(中文)

useEffect 是在浏览器绘制完成之后执行的,不会阻塞页面渲染。而 useLayoutEffect 是在 DOM 更新完成后、浏览器绘制之前同步执行的,所以它会阻塞渲染。一般情况下用 useEffect,只有在需要读取 DOM 布局或者避免页面闪烁的时候才用 useLayoutEffect。

 

Interview Spoken Answer (English)

useEffect runs after the browser has painted the screen, so it doesn’t block rendering. useLayoutEffect runs synchronously after DOM updates but before painting, so it blocks the browser from rendering until it finishes. In most cases we use useEffect, and only use useLayoutEffect when we need to measure DOM or prevent visual flickering.

 

Q9: useContext 的作用是什么?它和“props 层层传递”有什么本质区别?

请结合真实项目回答:

  1. 为什么 React 提供 Context?解决什么问题?
  2. Context 更新会发生什么(性能角度)?
  3. 什么情况下不适合用 Context?

  1. useContext 的作用

useContext 用于:跨层级共享数据,避免 props 层层传递(props drilling)

例如:

传统 props 传递问题:

中间组件只是“转发数据”,但不使用。

Context 方案:

👉 直接获取,不需要层层传递

  1. Context 更新会发生什么?

所有消费该 Context 的组件都会重新渲染

这是面试重点 ⚠️

Context 本质是:“全局订阅源”。

一旦 value 变化:

性能问题示例:

每次 render 都创建新对象:所有子组件都会更新

  1. 为什么 Context 不是状态管理工具?

因为它没有:

👉 所以它适合“低频变化数据”

  1. 什么情况不适合用 Context?

❌ 高频更新数据

例如:

👉 会导致全局 rerender

❌ 大规模状态拆分

Context 会变成“全局 rerender 触发器”

  1. 正确使用场景

✅ 适合:

❌ 不适合:


一句话总结: Context 用来“共享数据”,但不是“高性能状态管理工具”


Standard Answer (English)

  1. Purpose of useContext

useContext is used to avoid prop drilling by sharing data across deeply nested components.

  1. What happens when Context updates?

When the context value changes, all components consuming that context re-render.

This is because they subscribe to the context provider.

  1. Why not always use Context?

Because it lacks fine-grained control:

  1. When NOT to use it
  1. Good use cases

面试口语版(中文)

useContext 的作用是解决 props drilling 问题,可以让组件跨层级直接获取数据,而不需要一层层传递。但它的问题是,一旦 context 的 value 发生变化,所有消费它的组件都会重新渲染,所以它不适合放高频更新的数据,一般用于用户信息、主题这些变化不频繁的全局状态。

 

Interview Spoken Answer (English)

useContext is used to solve prop drilling by allowing deeply nested components to access shared data directly. However, when the context value changes, all consuming components will re-render. Because of this, it is not suitable for frequently changing state, and is better used for global but stable data like user info, theme, or language settings.

 

Q10: useReduceruseState 有什么区别?在什么情况下你会选择 useReducer 而不是 useState

请重点回答:

  1. 两者在状态管理模型上的区别
  2. useReducer 解决了 useState 的什么问题
  3. 真实项目中的典型使用场景

  1. 核心区别(本质模型)

useState

👉 “状态 = 直接赋值更新”

useReducer

👉 “状态 = action 驱动 + reducer 计算”

本质区别一句话

  1. useReducer 解决了 useState 的什么问题?

问题1:状态逻辑分散

👉 逻辑散落在组件各处

useReducer 解决:

👉 所有状态变化集中在 reducer

 

问题2:复杂状态更新难维护

例如:

 

问题3:难以追踪状态变化来源

useState: 👉 “哪里都可以改”

useReducer: 👉 “只能 dispatch”

  1. 什么时候用 useReducer?

✅ 适合场景

  1. useState 适合什么?
  1. 面试关键总结

👉 useState = “简单状态” 👉 useReducer = “状态逻辑管理”


一句话总结:当状态变化“有规则、有结构、有多个触发方式”,就用 useReducer


Standard Answer (English)

  1. Core difference

useState

Direct state updates:

useReducer

Action-driven state management:

  1. Problems useReducer solves
  1. When to use useReducer
  1. When to use useState

面试口语版(中文)

useState 适合简单状态,比如 input 或 toggle,而 useReducer 更适合复杂状态管理,因为它把所有状态更新逻辑集中在 reducer 里,通过 dispatch 来触发变化,这样状态变化更可控、更清晰,也更容易维护,尤其是在表单或者多状态联动的场景下。

 

Interview Spoken Answer (English)

useState is suitable for simple state like toggles or input values. useReducer is better for complex state management because it centralizes all state update logic inside a reducer function and uses dispatch to trigger changes. This makes state transitions more predictable and easier to maintain, especially in complex forms or multi-step workflows.

 

Q11: useMemouseCallbackReact.memo 三者在性能优化中分别扮演什么角色?它们之间有什么关系?

请重点回答:

  1. 三者分别优化的是什么(值 / 函数 / 组件)
  2. 为什么只用其中一个通常“不够”
  3. 在真实项目中它们如何配合使用

  1. 三者分别优化什么?

useMemo

👉 优化 “计算结果(值)”

 

useCallback

👉 优化 “函数引用”

 

React.memo

👉 优化 “组件重渲染”

  1. 它们的关系(重点)

👉 它们是层层配合的

场景:

问题:

正确优化组合:

  1. 为什么只用一个不够?

❌ 只用 React.memo

没用 useCallback → props 仍然变 → memo 失效

❌ 只用 useCallback

Child 没 memo → 还是 rerender

❌ 只用 useMemo

函数还是重新创建 → props 仍变化

👉 结论: 它们必须组合使用才有意义

  1. 本质理解(面试加分点)

React rerender 来源有三个:

👉 这三者优化的就是:

工具控制什么
useMemo
useCallback函数引用
React.memo组件是否重新渲染
  1. 什么时候“优化反而变慢”?

结论:不是所有地方都需要优化


一句话总结:React.memo 控制“组件”,useMemo 控制“值”,useCallback 控制“函数”


Standard Answer (English)

  1. What each one optimizes

useMemo: Optimizes computed values.

useCallback: Optimizes function references.

React.memo: Prevents unnecessary component re-renders.

  1. Relationship

They work together:

  1. Why one is not enough

Because:

All three are interconnected.


面试口语版(中文)

useMemo 用来缓存计算结果,useCallback 用来缓存函数引用,React.memo 用来防止组件在 props 没变化时重复渲染。它们是配合使用的,如果只用其中一个,往往优化是无效的,比如函数没用 useCallback,React.memo 也会失效。

 

Interview Spoken Answer (English)

useMemo is used to memoize computed values, useCallback is used to memoize function references, and React.memo prevents unnecessary re-renders of components when props do not change. They are meant to work together; using only one of them often does not provide real performance benefits because the others can still cause re-renders.

 

Q12: useRef 在解决“闭包陷阱(stale closure)”问题时是怎么用的?

请你结合真实场景说明:

  1. 什么是 stale closure(不用太学术)
  2. 为什么 useEffect + setInterval 容易出现这个问题
  3. useRef 是如何解决它的(核心机制)

  1. 什么是 stale closure(闭包旧值问题)

简单理解: 一个函数“记住”了它被定义时的变量值,即使这些变量在后续的组件渲染中已经发生了变化,该函数内部依然在使用那个旧的值。

示例:

为什么会出现 Stale Closure?

首次渲染count0useEffect 运行,创建了一个 setInterval 回调函数。

闭包形成:这个回调函数捕获了当前渲染周期的 count 变量(其值为 0)。

点击按钮:你点击了多次按钮,count 在 React 内部已经变成了 1, 2, 3...,组件也重新渲染了。

陷阱发生:但由于 useEffect 的依赖数组是 [],它不会重新运行,因此 setInterval 里持有的依然是第一次渲染时的函数引用。那个函数闭包里的 count 永远是 0

  1. 为什么 setInterval 很容易出问题?

问题原因:

👉 结果:count 永远不更新(或者永远是 0)

  1. useRef 是如何解决的?

核心思路:

👉 ref 不会因为 re-render 重新创建 👉 .current 始终是最新值

正确写法:

  1. 为什么它能解决问题?

useRef 特点:

👉 所以: setInterval 读取的是:

而不是闭包里的旧 count

  1. 本质理解(面试加分)

stale closure 本质:

👉 “闭包 + React render 生命周期不一致”

useRef 的解决方案:

👉 “绕过闭包,改成引用读取最新值”

  1. 实战场景

常见 bug:


一句话总结

👉 stale closure = “函数记住了旧 state” 👉 useRef = “用引用跳过闭包问题”


Standard Answer (English)

  1. What is stale closure?

A stale closure happens when a function “captures” an old state value from a previous render and keeps using it even after the state has changed.

  1. Why setInterval causes it

Because:

  1. How useRef fixes it

useRef provides a persistent object whose .current always holds the latest value without triggering re-renders.

So instead of relying on closure values, we read:

which is always up to date.


面试口语版(中文)

stale closure 的问题是函数会记住某一轮 render 的 state,所以在 setInterval 或异步回调里容易拿到旧值。useRef 可以解决这个问题,因为 ref 是一个一直存在的对象,它的 current 会保存最新值,不会受闭包影响,所以可以在定时器里直接读取最新状态。

 

Interview Spoken Answer (English)

A stale closure happens when a function captures state from an earlier render and keeps using that outdated value. This often occurs with setInterval or async callbacks inside useEffect. useRef solves this because it holds a persistent object whose .current always reflects the latest value, allowing us to access up-to-date state without relying on closures.